package com.se.hospitalmonitoring;

import java.awt.EventQueue;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.se.hospitalmonitoring.VitalSign.VitalSignMeasurement;

public class NurseStationControl extends Thread {
	ServerSocket serverSocket;
	ConcurrentLinkedQueue<Message> messageQueue;
	ArrayList<NurseStationObserver> observers = new ArrayList<NurseStationObserver>();

	/**
	 * Class to handle all the messages coming in from Bed side system
	 */
	class MessageHandler extends Thread {
		ConcurrentLinkedQueue<Message> messageQueue;
		Socket socket;
		ObjectInputStream in;

		/**
		 * Constructor
		 * 
		 * @param messageQueue
		 *            the queue to put message in
		 * 
		 * @param socket
		 *            the socket to obtain message from
		 */
		public MessageHandler(ConcurrentLinkedQueue<Message> messageQueue,
			Socket socket) {
			this.messageQueue = messageQueue;
			this.socket = socket;
		}

		/**
		 * Adds the object read from socket into the queue
		 */
		@Override
		public void run() {
			try {
				in = new ObjectInputStream(new BufferedInputStream(socket
					.getInputStream()));
			}
			catch (IOException e) {
				e.printStackTrace();
			}

			while (true) {
				try {
					Message m = (Message) in.readObject();
					messageQueue.add(m);
				}
				catch (IOException e) {
					e.printStackTrace();
				}
				catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}

	class MessageProcessor extends Thread {
		@Override
		public void run() {
			while (true) {
				if (messageQueue.size() != 0) {
					onMessageReceived(messageQueue.poll());
				}
			}
		}
	}

	/**
	 * Constructor
	 * 
	 * @param port
	 *            the port number the server should listen on
	 */
	public NurseStationControl(int port) {
		try {
			serverSocket = new ServerSocket(port);
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		messageQueue = new ConcurrentLinkedQueue<Message>();
		MessageProcessor messageProcessor = new MessageProcessor();
		messageProcessor.start();
	}

	/**
	 * Called when a new message is received
	 * 
	 * @param message
	 *            The message that was received
	 */
	public synchronized void onMessageReceived(Message message) {

		System.out.println("Message Recieved: " + message.type);

		switch (message.type) {
			case Startup:
				onBedStartup(message.bedId);
				break;
			case VitalSignUpdate:
				onNewVitalSign(message.bedId,
					(VitalSignMeasurement) message.value);
				break;
			case CallNotification:
				onNewCallNotification(message.bedId,
					(CallNotification) message.value);
				break;
			case AlarmNotification:
				onNewAlarmNotification(message.bedId,
					(AlarmNotification) message.value);
				break;
			case CancelCall:
				onCallCanceled(message.bedId);
				break;
			case CancelAlarm:
				onAlarmCanceled(message.bedId);
				break;
			default:
				break;
		}
	}

	/**
	 * Called when a bed joins
	 * 
	 * @param bedId
	 *            The new bed id
	 */
	public synchronized void onBedStartup(long bedId) {
		boolean isNew = BedAssignment.getInstance().addBed(bedId);
		if (isNew) {
			for (NurseStationObserver observer : observers)
				observer.onNewBed(bedId);
		}
	}

	/**
	 * Called when a new vital sign is received
	 * 
	 * @param bedId
	 *            The bed id
	 * @param vitalSign
	 *            The vital sign measurement that was received
	 */
	public synchronized void onNewVitalSign(long bedId,
		VitalSignMeasurement vitalSign) {
		for (NurseStationObserver observer : observers)
			observer.onNewVitalSign(bedId, vitalSign);
	}

	/**
	 * Called when a call is triggered
	 * 
	 * @param bedId
	 *            The bed id that triggered the call
	 * @param callNotification
	 *            The call object
	 */
	public synchronized void onNewCallNotification(long bedId,
		CallNotification callNotification) {
		for (NurseStationObserver observer : observers)
			observer.onNewCallNotification(bedId, callNotification);
	}

	/**
	 * Called when an alarm is triggered
	 * 
	 * @param bedId
	 *            The bed that triggered the alarm
	 * @param alarmNotification
	 *            The alarm object
	 */
	public synchronized void onNewAlarmNotification(long bedId,
		AlarmNotification alarmNotification) {
		for (NurseStationObserver observer : observers)
			observer.onNewAlarmNotification(bedId, alarmNotification);
	}

	/**
	 * Called when the call button is reset
	 * 
	 * @param bedId
	 *            The bed that reset the call button
	 */
	public synchronized void onCallCanceled(long bedId) {
		for (NurseStationObserver observer : observers)
			observer.onCallCanceled(bedId);
	}

	/**
	 * Called when an alarm is reset
	 * 
	 * @param bedId
	 *            The bed that reset the alarm
	 */
	public synchronized void onAlarmCanceled(long bedId) {
		for (NurseStationObserver observer : observers)
			observer.onAlarmCanceled(bedId);
	}

	/**
	 * Adds the given observer to observe incoming messages
	 * 
	 * @param observer
	 *            The observer to add
	 */
	public void addObserver(NurseStationObserver observer) {
		observers.add(observer);
	}

	/**
	 * Removes the given observer
	 * 
	 * @param observer
	 *            The observer to remove
	 */
	public void removeObserver(NurseStationObserver observer) {
		observers.remove(observer);
	}

	/**
	 * Start the server
	 */
	@Override
	public void run() {
		SocketAddress address = serverSocket.getLocalSocketAddress();
		InetSocketAddress inetAddress = (InetSocketAddress) address;
		System.out.println("NurseStation Server started. Port: "
			+ inetAddress.getPort());
		while (true) {
			Socket socket = null;
			try {
				socket = serverSocket.accept();
				System.out.println("Connection received from "
					+ socket.getInetAddress().getHostName());
			}
			catch (IOException e) {
				e.printStackTrace();
			}
			MessageHandler messageHandler = new MessageHandler(messageQueue,
				socket);
			messageHandler.start();
		}

	}

	/**
	 * Starts the nurse station server program
	 * 
	 * @param args
	 *            unused
	 */
	public static void main(String args[]) {
		final NurseStationControl nurseStation = new NurseStationControl(5555);
		nurseStation.start();
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					NurseStationInterface frame = new NurseStationInterface();
					frame.setVisible(true);
					nurseStation.addObserver(frame);
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public static interface NurseStationObserver {
		void onNewVitalSign(long bedId, VitalSignMeasurement vitalSign);

		void onNewCallNotification(long bedId, CallNotification callNotification);

		void onNewAlarmNotification(long bedId,
			AlarmNotification alarmNotification);

		void onNewBed(long bedId);

		void onCallCanceled(long bedId);

		void onAlarmCanceled(long bedId);
	}
}
